/// <reference types="@koishijs/canvas" />

import { h } from "koishi"

export const name = "numberblocks"
export const inject = ["canvas", "autoDeleteResponse"]

/** @param {import("koishi").Context} ctx */
export async function apply(ctx) {
  async function run({ options }, input) {
    input = input.replace(/\r\n?/g, "\n")
    /** @type {import("@koishijs/canvas").Canvas} */
    let canvas
    try {
      if (options.gap < 0 || options.gap > 0.02) throw "invalid-gap"
      if (options.border < 0 || options.border > 0.2) throw "invalid-border"
      if (options.move) {
        const vals = options.move.split(/\s+/g).map(Number)
        if (vals.length !== 2 || vals.some(n => !Number.isInteger(n))) throw "invalid-move"
        const [x, y] = vals
        input = translateNumberBlocks(input, { x, y })
      }

      await drawNumberBlocks(input, {
        async createCanvas(width, height) {
          if (canvas) canvas.dispose()
          return canvas = await ctx.canvas.createCanvas(width, height)
        },
        bgColor: options.background,
        viewPort: options.viewport,
        border: options.border,
        gap: options.gap,
        showRuler: options.ruler,
      })
      return (options.move ? input : "") + h.image(await canvas.toBuffer("image/png"), "image/png")
    } catch (err) {
      if (err instanceof Error) ctx.logger.error(err)
      return session.i18n("." + String(err))
    } finally {
      canvas?.dispose()
    }
  }

  ctx.command("numberblocks <input:rawtext>", { checkArgCount: true, strictOptions: true })
    .option("background", "-b <color:string>", { fallback: "#ffffff" })
    .option("viewport", '-v <"[top left] height width":string> ')
    .option("gap", "-g <0~0.02:number>", { fallback: 0.005, params: [0.005] })
    .option("border", "-B <0~0.2:number>", { fallback: 0.1, params: [0.1] })
    .option("ruler", "-r")
    .option("move", '-m <"rows cols">')
    .action(ctx.autoDeleteResponse.action(async (argv, input) => {
      const { session, root } = argv
      const result = await run(argv, input)
      return result
    }))

  ctx.i18n.define("zh-CN", "commands.numberblocks", {
    description: "Numberblocks 简图生成器",
    usage:
      "输入格式：每行一个坐标（或分号分隔），空格或英文逗号分隔，1,1为左上角，先行后列\n" +
      "可用冒号表示区间，可正序或倒序，默认先行后列，在前面加上字母v并在v和坐标之间加上空格，可改为先列后行\n" +
      "在前面加上字母t可使用“特殊十”功能，t后面需紧接一至两个字母u（上）、d（下）、l（左）、r（右）之一，然后空格后接上单个坐标，表示矩形的左上角或阶梯形的直角顶点所在\n" +
      "*目前t开头只支持tr trr td tdd tul tlu tur tru tdl tld tdr trd\n" +
      "tr为1行10列，如tr 2 2为2行2列至2行11列的一个长条，以下可类推\n" +
      "trr为2行5列，tdd为5行2列，td为10行1列\n" +
      "t后接两个相互垂直的方向代表一个4阶Step Squads，后面的坐标代表阶梯的直角顶点\n" +
      "如tul或tlu为直角顶点在右下角（向左延伸），其余类推\n" +
      "支持同时录入多个积木，不同的积木之间请用---或===（至少三个英文横号或等号）作为分隔线\n" +
      "单个积木最大支持99999（五位数）",
    examples:
      "一个竖条 7：nb 1:7,1\n" +
      "一个横条 7，显示标尺：nb -r 1,1:7\n" +
      '从第 2 行第 3 列开始的 3 个连续竖条 10，显示从第 1 行第 1 列开始、宽 9 高 15 的区域，显示标尺：nb -rv "1 1 15 9" td 2 3;td 2 4;td 2 5\n' +
      '将这 3 个竖条 10 向下平移 4 格，向左平移 2 格（同时输出平移后的代码），裁剪区域不变，不显示标尺：nb -m "4 -2" -v "1 1 15 9" td 2 3;td 2 4;td 2 5',
    options: {
      background: "背景颜色（CSS 颜色值，HEX 色号需加井号）",
      viewport: "裁剪区域",
      gap: "缝隙大小，默认 {0}",
      border: "边框粗细，默认 {0}",
      ruler: "显示标尺",
      move: "位移",
    },
    messages: {
      "invalid-gap": "缝隙大小值超出范围限制…",
      "invalid-border": "边框粗细值超出范围限制…",
      "invalid-move": "位移值无效…",
      "invalid-viewport": "裁剪区域无效…",
      "invalid-input": "无效输入…"
    },
  })
}

// 以下于 2024 年 7 月改写自 http://ci3.top/nb.htm 的部分源代码

const { drawNumberBlocks, translateNumberBlocks } = function () {
  var maxBlocks = 99999;
  var dir = [[-1,0], [1,0], [0,-1], [0,1]];
  var ones = ["#ED1C24", "#ED1C24", "#FF9F27", "#FFF200", "#25CB00", "#10E4FF", "#680DFF", "#B06DFE", "#FF02E6", "#D8D8D8", "#B0B0B0", "#909090"];
  var tens = ["#FFFFFF", "#FCA39E", "#FEE0B8", "#FBFFA0", "#AAFF99", "#91F5FE", "#9582FE", "#D6ADFD", "#FA8DFA", "#E0E0E0", "#BCBCBC", "#A0A0A0"];
  var hun1 = ["#FFFFFF", "#FF8888", "#ECC573", "#EBEB3F", "#8AD15E", "#78D7E4", "#8A72DE", "#C1A3E1", "#E68CE5", "#DEDEDE", "#B9B9B9", "#9A9A9A"];
  var hun2 = ["#FFFFFF", "#FC6969", "#E8BB30", "#E1DE17", "#6BBB49", "#5AC4DE", "#7A5FDA", "#B791DD", "#E16DDC", "#D3D3D3", "#ABABAB", "#888888"];
  var bord = ["#ED1C24", "#ED1C24", "#F09F00", "#CFCC00", "#22AB00", "#00B0D5", "#5421C8", "#A04DD2", "#C118B6", "#C0C0C0", "#9C9C9C", "#727272"];
  var blockSize = 20;
  var rulerW = 0.3;
  var i, j, k;
  var _offsetX,_offsetY;

  /** @type {(thing: unknown) => boolean} */
  const nonEmpty = Boolean;
  /** @type {(string: string, from: string, to: string) => string} */
  const replace = Function.prototype.call.bind(String.prototype.replaceAll);

  /**
   * @param {string} raw
   * @param {{ x: number, y: number }} _opt
   */
  function move(raw, {x,y}) {
    _offsetX = x;
    _offsetY = y;
    return raw.split(/===|---/g).map(s => s.split(/[;；\n]/g).map(plusline).filter(nonEmpty).join(";")).filter(nonEmpty).join("\n---\n");
  }

  function normalize(input){
    var str = input;
    str = str.toLowerCase();
    str = replace(str, "===", "---");
    str = replace(str, " ", ",");
    str = replace(str, "　", ",");
    str = replace(str, "\t", ",");
    str = replace(str, "，", ",");
    str = replace(str, "\n", ";");
    str = replace(str, "；", ";");
    str = replace(str, "：", ":");
    return str;
  }

  function inteAnal(xInte, yInte, mark)
  {
    var list1, tmp, xFrom, xTo, yFrom, yTo, xDir, yDir, xi, yi;
    var res = [];
    list1 = xInte.split(":");
    if (list1.length == 1){
      tmp = parseInt(list1[0]);
      if (isNaN(tmp)) return [];
      xFrom = tmp; xTo = tmp;
    }
    else
    {
      xFrom = parseInt(list1[0]);
      if (isNaN(xFrom)) return [];
      xTo = parseInt(list1[1]);
      if (isNaN(xTo)) return [];
    }
    list1 = yInte.split(":");
        if (list1.length == 1){
            tmp = parseInt(list1[0]);
            if (isNaN(tmp)) return [];
            yFrom = tmp; yTo = tmp;
        }
        else
        {
            yFrom = parseInt(list1[0]);
            if (isNaN(yFrom)) return [];
            yTo = parseInt(list1[1]);
            if (isNaN(yTo)) return [];
        }
    if (xFrom <= xTo) xDir = 1; else xDir = -1;
    if (yFrom <= yTo) yDir = 1; else yDir = -1;
    if (mark == 1){
      for (yi = yFrom; yi*yDir <= yTo*yDir; yi += yDir)
      for (xi = xFrom; xi*xDir <= xTo*xDir; xi += xDir)
        res.push([xi, yi]);
    }
    else{
      for (xi = xFrom; xi*xDir <= xTo*xDir; xi += xDir)
      for (yi = yFrom; yi*yDir <= yTo*yDir; yi += yDir)
        res.push([xi, yi]);
    }
    return res;
  }

  function readline(line){
    var temp = line.split(",");
    var tempX, tempY, ii, jj;
    var res = [];
    temp = temp.filter(nonEmpty);
    if (temp.length==0) return [];
    if (temp[0].charAt(0) == "t"){
      if (temp.length < 3) return [];
      tempX = parseInt(temp[1]);
      tempY = parseInt(temp[2]);
      if (isNaN(tempX)) return [];
      if (isNaN(tempY)) return [];
      if (temp[0].charAt(1) == "d"){
        if (temp[0].charAt(2) == "l"){
          for (jj = 0; jj <= 3; jj++)
          for (ii = 0; ii <= 3-jj; ii++)
            res.push([tempX+ii, tempY-jj]);
        }
        else if (temp[0].charAt(2) == "r"){
          for (jj = 0; jj <= 3; jj++)
          for (ii = 0; ii <= 3-jj; ii++)
            res.push([tempX+ii, tempY+jj]);
        }
        else if (temp[0].charAt(2) == "d"){
          for (jj = 0; jj < 2; jj++)
          for (ii = 0; ii < 5; ii++)
            res.push([tempX+ii, tempY+jj]);
        }
        else{
          for (ii = 0; ii < 10; ii++)
            res.push([tempX+ii, tempY]);
        }
      }
      else if (temp[0].charAt(1) == "u"){
        if (temp[0].charAt(2) == "l"){
          for (jj = 0; jj <= 3; jj++)
          for (ii = 0; ii <= 3-jj; ii++)
            res.push([tempX-ii, tempY-jj]);
        }
        else if (temp[0].charAt(2) == "r"){
          for (jj = 0; jj <= 3; jj++)
          for (ii = 0; ii <= 3-jj; ii++)
            res.push([tempX-ii, tempY+jj]);
        }
      }
      else if (temp[0].charAt(1) == "l"){
        if (temp[0].charAt(2) == "d"){
          for (ii = 0; ii <= 3; ii++)
          for (jj = 0; jj <= 3-ii; jj++)
            res.push([tempX+ii, tempY-jj]);
        }
        else if (temp[0].charAt(2) == "u"){
          for (ii = 0; ii <= 3; ii++)
          for (jj = 0; jj <= 3-ii; jj++)
            res.push([tempX-ii, tempY-jj]);
        }
      }
      else if (temp[0].charAt(1) == "r"){
        if (temp[0].charAt(2) == "d"){
          for (ii = 0; ii <= 3; ii++)
          for (jj = 0; jj <= 3-ii; jj++)
            res.push([tempX+ii, tempY+jj]);
        }
        else if (temp[0].charAt(2) == "u"){
          for (ii = 0; ii <= 3; ii++)
          for (jj = 0; jj <= 3-ii; jj++)
            res.push([tempX-ii, tempY+jj]);
        }
        else if (temp[0].charAt(2) == "r"){
          for (ii = 0; ii < 2; ii++)
          for (jj = 0; jj < 5; jj++)
            res.push([tempX+ii, tempY+jj]);
        }
        else{
          for (jj = 0; jj < 10; jj++)
            res.push([tempX, tempY+jj]);
        }
      }
      return res;
    }
    else if (temp[0].charAt(0) == "v"){
      if (temp.length < 3) return [];
      return inteAnal(temp[1], temp[2], 1);
    }
    else if (temp[0].charAt(0) == "h"){
      if (temp.length < 3) return [];
      return inteAnal(temp[1], temp[2], 0);
    }
    else{
      if (temp.length < 2) return [];
      return inteAnal(temp[0], temp[1], 0);
    }
    return [[parseInt(temp[0]), parseInt(temp[1])]];
  }
  function plusline(line){
    var x=_offsetX;
    var y=_offsetY;
    var temp = line.split(/[, \u3000\t，]/g);
    var tempX, tempY, ii, jj;
    var res = [];
    temp = temp.filter(nonEmpty);
    if (temp.length==0) return "";
    if (temp[0].charAt(0) == "t"){
      if (temp.length < 3) return "";
      tempX = parseInt(temp[1]);
      tempY = parseInt(temp[2]);
      if (tempX == NaN) return "";
      if (tempY == NaN) return "";
      if (temp[0].charAt(1) == "d"){
        if (temp[0].charAt(2) == "l"){
          return "tdl "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "r"){
          return "tdr "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "d"){
          return "tdd "+(tempX+x)+","+(tempY+y);
        }
        else{
          return "td "+(tempX+x)+","+(tempY+y);
        }
      }
      else if (temp[0].charAt(1) == "u"){
        if (temp[0].charAt(2) == "l"){
          return "tul "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "r"){
          return "tur "+(tempX+x)+","+(tempY+y);
        }
      }
      else if (temp[0].charAt(1) == "l"){
        if (temp[0].charAt(2) == "d"){
          return "tld "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "u"){
          return "tlu "+(tempX+x)+","+(tempY+y);
        }
      }
      else if (temp[0].charAt(1) == "r"){
        if (temp[0].charAt(2) == "d"){
          return "trd "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "u"){
          return "tru "+(tempX+x)+","+(tempY+y);
        }
        else if (temp[0].charAt(2) == "r"){
          return "trr "+(tempX+x)+","+(tempY+y);
        }
        else{
          return "tr "+(tempX+x)+","+(tempY+y);
        }
      }
      return res;
    }
    else if (temp[0].charAt(0) == "v"){
      var list1 = temp[1].split(":");
      var list2 = temp[2].split(":");
      var l1 = new Array();
      var l2 = new Array();
      for (var i=0;i<list1.length;i++)l1.push(parseInt(list1[i])+x);
      for (var i=0;i<list2.length;i++)l2.push(parseInt(list2[i])+y);
      return "v "+l1.join(":")+","+l2.join(":");
    }
    else if (temp[0].charAt(0) == "h"){
      var list1 = temp[1].split(":");
      var list2 = temp[2].split(":");
      var l1 = new Array();
      var l2 = new Array();
      for (var i=0;i<list1.length;i++)l1.push(parseInt(list1[i])+x);
      for (var i=0;i<list2.length;i++)l2.push(parseInt(list2[i])+y);
      return "h "+l1.join(":")+","+l2.join(":");
    }
    else{
      var list1 = temp[0].split(":");
      var list2 = temp[1].split(":");
      var l1 = new Array();
      var l2 = new Array();
      for (var i=0;i<list1.length;i++)l1.push(parseInt(list1[i])+x);
      for (var i=0;i<list2.length;i++)l2.push(parseInt(list2[i])+y);
      return l1.join(":")+","+l2.join(":");
    }
    return "";
  }

  function chartNew() {
    return Object.create(null)
  }
  function chartGet(chart, x, y) {
    return chart[[~~x, ~~y]] || 0;
  }
  function chartSet(chart, x, y, val) {
    chart[[~~x, ~~y]] = val;
  }

  /**
   * @param {string} raw
   * @param {{ createCanvas: (width: number, height: number) => Promise<HTMLCanvasElement>, bgColor: string, viewPort?: string, border: number, gap: number, showRuler: boolean }} _opt
   */
  async function ok(raw, {createCanvas, bgColor, viewPort, border, gap, showRuler}) {
    var canvasX = 1000;
    var canvasY = 1000;
    var minX = canvasX;
    var minY = canvasY;
    var maxH = 1;
    var maxW = 1;
    if (viewPort) {
      const vals = viewPort.split(/[,\s]+/g).map(Number)
      if ((vals.length !== 2 && vals.length !== 4) || vals.some(n => !Number.isInteger(n) || n <= 0 || n >= 1000))
        throw "invalid-viewport"
      if (vals.length === 2) {
        minX = 1
        minY = 1
        maxH = vals[0]
        maxW = vals[1]
      } else {
        minX = vals[0]
        minY = vals[1]
        maxH = minX + vals[2] - 1
        maxW = minY + vals[3] - 1
      }
    }

    var wid=border /* 0.1 */;
    var blockRatio = 1 - gap /* 0.005 */;
    var from = (1 - blockRatio) / 2;
    var to = 1 - from;
    var shorten = 0.05;
    var lineExtend = 0.3;
    raw = normalize(raw);
    var input = raw.split("---");
    var each;
    var _charts = [];
    for (each = 0; each < input.length; each++)
    {
      var input1 = input[each].split(";");
      var lineN = input1.length;
      var chart = chartNew();
      var n = 0;
      var coo = new Array();
      var temp, temp1, temp2;
      for (i = 0; i < lineN; i++){
        temp = readline(input1[i]);
        for (j = 0; j < temp.length; j++){
          temp1 = temp[j][0];
          temp2 = temp[j][1];
          if (n < maxBlocks)
          if (!isNaN(temp1))
          if (!isNaN(temp2))
          if (temp1 >= (viewPort ? minX : 1) && temp1 <= (viewPort ? maxH : canvasX))
          if (temp2 >= (viewPort ? minY : 1) && temp2 <= (viewPort ? maxW : canvasY))
          if (chartGet(chart, temp1, temp2) == 0)
          {
            chartSet(chart, temp1, temp2, 1);
            coo[++n] = [temp1, temp2];
            if (!viewPort) {
              if (temp1 > maxH) maxH = temp1;
              if (temp1 < minX) minX = temp1;
              if (temp2 > maxW) maxW = temp2;
              if (temp2 < minY) minY = temp2;
            }
          }
        }
      }
      _charts.push({chart,n,coo});
    }
    if (_charts.every(({ coo }) => !coo.length)) throw "invalid-input"
    //console.log({ maxH, maxW, minX, minY })

    var picH = (maxH-minX+3) * blockSize;
    var picW = (maxW-minY+3) * blockSize;
    var c = await createCanvas(picW, picH);
    var ctx = c.getContext("2d");

    ctx.clearRect(0, 0, picW, picH);
    ctx.fillStyle = "transparent";
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, picW, picH);

    if (showRuler) {
      for (i = minX; i <= maxH; i++){
        if (i % 100 == 0){
          for (j = 0; j < 10; j++){
            if (j%2==0) ctx.fillStyle = hun1[Math.round(i/100) % 10];
            else ctx.fillStyle = hun2[Math.round(i/100) % 10];
            ctx.fillRect(0, (i-minX+1+j/10)*blockSize, rulerW*blockSize, blockSize/10);
          }
          ctx.strokeStyle = bord[Math.round(i/100) % 10];
          ctx.strokeRect(0, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
          for (j = 0; j < 10; j++){
            if (j%2==0) ctx.fillStyle = hun1[Math.round(i/100) % 10];
            else ctx.fillStyle = hun2[Math.round(i/100) % 10];
            ctx.fillRect(picW - rulerW*blockSize, (i-minX+1+j/10)*blockSize, rulerW*blockSize, blockSize/10);
          }
          ctx.strokeStyle = bord[Math.round(i/100) % 10];
          ctx.strokeRect(picW - rulerW*blockSize, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
        }
        else if (i % 10 == 0){
          if (i % 100 == 70){
            for (j = 1; j <= 7; j++){
              ctx.fillStyle = tens[j];
              ctx.fillRect(0, (i-minX+1+(j-1)/7)*blockSize, rulerW*blockSize, blockSize/7);
              ctx.fillRect(picW - rulerW*blockSize, (i-minX+1+(j-1)/7)*blockSize, rulerW*blockSize, blockSize/7);
            }
          }
          else if (i % 100 == 90){
            for (j = 1; j <= 3; j++){
              ctx.fillStyle = tens[j+8];
              ctx.fillRect(0, (i-minX+1+(j-1)/3)*blockSize, rulerW*blockSize, blockSize/3);
              ctx.fillRect(picW - rulerW*blockSize, (i-minX+1+(j-1)/3)*blockSize, rulerW*blockSize, blockSize/3);
            }
          }
          else{
            if (i % 100 == 10) ctx.fillStyle = tens[0];
            else ctx.fillStyle = tens[Math.round(i/10) % 10];
            ctx.fillRect(0, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
            ctx.fillRect(picW - rulerW*blockSize, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
          }
          if (i % 100 == 90) ctx.strokeStyle = bord[11];
          else ctx.strokeStyle = bord[Math.round(i/10) % 10];
          ctx.strokeRect(0, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
          ctx.strokeRect(picW - rulerW*blockSize, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
        }
        else
        {
          if (i % 10 == 7){
            for (j = 1; j <= 7; j++){
              ctx.fillStyle = ones[j];
              ctx.fillRect(0, (i-minX+1+(j-1)/7)*blockSize, rulerW*blockSize, blockSize/7);
              ctx.fillRect(picW - rulerW*blockSize, (i-minX+1+(j-1)/7)*blockSize, rulerW*blockSize, blockSize/7);
            }
          }
          else if (i % 10 == 9){
            for (j = 1; j <= 3; j++){
              ctx.fillStyle = ones[j+8];
              ctx.fillRect(0, (i-minX+1+(j-1)/3)*blockSize, rulerW*blockSize, blockSize/3);
              ctx.fillRect(picW - rulerW*blockSize, (i-minX+1+(j-1)/3)*blockSize, rulerW*blockSize, blockSize/3);
            }
          }
          else{
            ctx.fillStyle = ones[i % 10];
            ctx.fillRect(0, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
            ctx.fillRect(picW - rulerW*blockSize, (i-minX+1)*blockSize, rulerW*blockSize, blockSize);
          }
        }
      }
      for (i = minY; i <= maxW; i++){
        if (i % 100 == 0){
          for (j = 0; j < 10; j++){
            if (j%2==0) ctx.fillStyle = hun1[Math.round(i/100) % 10];
            else ctx.fillStyle = hun2[Math.round(i/100) % 10];
            ctx.fillRect((i-minY+1+j/10)*blockSize, 0, blockSize/10, rulerW*blockSize);
          }
          ctx.strokeStyle = bord[Math.round(i/100) % 10];
          ctx.strokeRect((i-minY+1)*blockSize, 0, blockSize, rulerW*blockSize);
          for (j = 0; j < 10; j++){
            if (j%2==0) ctx.fillStyle = hun1[Math.round(i/100) % 10];
            else ctx.fillStyle = hun2[Math.round(i/100) % 10];
            ctx.fillRect((i-minY+1+j/10)*blockSize, picH - rulerW*blockSize, blockSize/10, rulerW*blockSize);
          }
          ctx.strokeStyle = bord[Math.round(i/100) % 10];
          ctx.strokeRect(i-minY+1*blockSize, picH - rulerW*blockSize, blockSize, rulerW*blockSize);
        }
        else if (i % 10 == 0){
          if (i % 100 == 70){
            for (j = 1; j <= 7; j++){
              ctx.fillStyle = tens[j];
              ctx.fillRect((i-minY+1+(j-1)/7)*blockSize, 0, blockSize/7, rulerW*blockSize);
              ctx.fillRect((i-minY+1+(j-1)/7)*blockSize, picH - rulerW*blockSize, blockSize/7, rulerW*blockSize);
            }
          }
          else if (i % 100 == 90){
            for (j = 1; j <= 3; j++){
              ctx.fillStyle = tens[j+8];
              ctx.fillRect((i-minY+1+(j-1)/3)*blockSize, 0, blockSize/3, rulerW*blockSize);
              ctx.fillRect((i-minY+1+(j-1)/3)*blockSize, picH - rulerW*blockSize, blockSize/3, rulerW*blockSize);
            }
          }
          else{
            if (i % 100 == 10) ctx.fillStyle = tens[0];
            else ctx.fillStyle = tens[Math.round(i/10) % 10];
            ctx.fillRect((i-minY+1)*blockSize, 0, blockSize, rulerW*blockSize);
            ctx.fillRect((i-minY+1)*blockSize, picH - rulerW*blockSize, blockSize, rulerW*blockSize);
          }
          if (i % 100 == 90) ctx.strokeStyle = bord[11];
          else ctx.strokeStyle = bord[Math.round(i/10) % 10];
          ctx.strokeRect((i-minY+1)*blockSize, 0, blockSize, rulerW*blockSize);
          ctx.strokeRect((i-minY+1)*blockSize, picH - rulerW*blockSize, blockSize, rulerW*blockSize);
        }
        else
        {
          if (i % 10 == 7){
            for (j = 1; j <= 7; j++){
              ctx.fillStyle = ones[j];
              ctx.fillRect((i-minY+1+(j-1)/7)*blockSize, 0, blockSize/7, rulerW*blockSize);
              ctx.fillRect((i-minY+1+(j-1)/7)*blockSize, picH - rulerW*blockSize, blockSize/7, rulerW*blockSize);
            }
          }
          else if (i % 10 == 9){
            for (j = 1; j <= 3; j++){
              ctx.fillStyle = ones[j+8];
              ctx.fillRect((i-minY+1+(j-1)/3)*blockSize, 0, blockSize/3, rulerW*blockSize);
              ctx.fillRect((i-minY+1+(j-1)/3)*blockSize, picH - rulerW*blockSize, blockSize/3, rulerW*blockSize);
            }
          }
          else{
            ctx.fillStyle = ones[i % 10];
            ctx.fillRect((i-minY+1)*blockSize, 0, blockSize, rulerW*blockSize);
            ctx.fillRect((i-minY+1)*blockSize, picH - rulerW*blockSize, blockSize, rulerW*blockSize);
          }
        }
      }
    }

    ctx.lineWidth = blockSize * wid;
    ctx.translate((1 - minY) * blockSize, (1 - minX) * blockSize);

    for (const {chart,n,coo} of _charts) {
      ctx.fillStyle = '#000000';
      for (i = minX; i < maxH; i++){
        for (j = minY; j < maxW; j++){
          if (chartGet(chart, i, j) == 1 && chartGet(chart, i+1, j) == 1 && chartGet(chart, i, j+1) == 1 && chartGet(chart, i+1, j+1) == 1){
            ctx.fillRect((j+0.5)*blockSize, (i+0.5)*blockSize, 1.0*blockSize, 1.0*blockSize);
          }
        }
        for (j = minY; j <= maxW; j++){
          if (chartGet(chart, i, j) == 1 && chartGet(chart, i+1, j) == 1){
            ctx.fillRect((j+from+shorten)*blockSize, (i+0.5)*blockSize, (blockRatio-shorten*2)*blockSize, 1.0*blockSize);
          }
        }
      }
      for (i = 1; i <= maxH; i++){
        for (j = 1; j < maxW; j++){
          if (chartGet(chart, i, j) == 1 && chartGet(chart, i, j+1) == 1){
            ctx.fillRect((j+0.5)*blockSize, (i+from+shorten)*blockSize, 1.0*blockSize, (blockRatio-shorten*2)*blockSize);
          }
        }
      }
      var p10000 = Math.floor((n+0.5)/10000) % 10;
      var p1000 = Math.floor((n+0.5)/1000) % 10;
      var p100 = Math.floor((n+0.5)/100) % 10;
      var p10 = Math.floor((n+0.5)/10) % 10;
      var p1 = n % 10;
      var nowColor;
      var fromLineExtend;
      var toLineExtend;
      for (i = 1; i <= p1; i++){
        if (p1 == 7) nowColor = i;
        else if (p1 == 9) nowColor = 9 + Math.floor((i-0.5)/3);
        else nowColor = p1;
        j = p10000*10000 + p1000*1000 + p100*100 + p10*10 + i;
        {
          ctx.fillStyle = ones[nowColor];
          ctx.fillRect((coo[j][1]+from)*blockSize, (coo[j][0]+from)*blockSize, blockRatio*blockSize, blockRatio*blockSize);
        }
      }
      for (i = 1; i <= p10000; i++){
        if (p10000 == 7) nowColor = i;
        else if (p10000 == 9) nowColor = 9 + Math.floor((i-0.5)/3);
        else if (p10000 == 1) nowColor = 0;
        else nowColor = p10000;
        for (j = (i-1)*10000+1; j <= i*10000; j++){
          chartSet(chart, coo[j][0], coo[j][1], 2);
          ctx.fillStyle = tens[nowColor];
          ctx.fillRect((coo[j][1]+from)*blockSize, (coo[j][0]+from)*blockSize, blockRatio*blockSize, blockRatio*blockSize);
        }
        ctx.strokeStyle = bord[nowColor];
        for (j = (i-1)*10000+1; j <= i*10000; j++){
          for (k=0; k<4; k++){
            temp1 = coo[j][0] + dir[k][0];
            temp2 = coo[j][1] + dir[k][1];
            if (chartGet(chart, temp1, temp2) != 2){
              ctx.beginPath();
              fromLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]+dir[k][1], coo[j][1]+dir[k][0]) == 2 && chartGet(chart, temp1+dir[k][1], temp2+dir[k][0]) != 2)
                fromLineExtend = 0;
              toLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]-dir[k][1], coo[j][1]-dir[k][0]) == 2 && chartGet(chart, temp1-dir[k][1], temp2-dir[k][0]) != 2)
                toLineExtend = 0;
              ctx.moveTo((coo[j][1] + 1/2 + dir[k][1]/2 + dir[k][0]/2 +dir[k][0]*wid*fromLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 + dir[k][1]/2+dir[k][1]*wid*fromLineExtend)*blockSize);
              ctx.lineTo((coo[j][1] + 1/2 + dir[k][1]/2 - dir[k][0]/2 -dir[k][0]*wid*toLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 - dir[k][1]/2-dir[k][1]*wid*toLineExtend)*blockSize);
              ctx.stroke();
            }
          }
        }
        for (j = (i-1)*10000+1; j <= i*10000; j++)
          chartSet(chart, coo[j][0], coo[j][1], 3);
      }
      for (i = 1; i <= p1000; i++){
        if (p1000 == 7) nowColor = i;
        else if (p1000 == 9) nowColor = 9 + Math.floor((i-0.5)/3);
        else nowColor = p1000;
        for (j = p10000*10000+(i-1)*1000+1; j <= p10000*10000+i*1000; j++){
          chartSet(chart, coo[j][0], coo[j][1], 2);
          ctx.fillStyle = ones[nowColor];
          ctx.fillRect((coo[j][1]+from)*blockSize, (coo[j][0]+from)*blockSize, blockRatio*blockSize, blockRatio*blockSize);
        }
        ctx.strokeStyle = bord[nowColor];
        for (j = p10000*10000+(i-1)*1000+1; j <= p10000*10000+i*1000; j++){
          for (k=0; k<4; k++){
            temp1 = coo[j][0] + dir[k][0];
            temp2 = coo[j][1] + dir[k][1];
            if (chartGet(chart, temp1, temp2) != 2){
              ctx.beginPath();
              fromLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]+dir[k][1], coo[j][1]+dir[k][0]) == 2 && chartGet(chart, temp1+dir[k][1], temp2+dir[k][0]) != 2)
                fromLineExtend = 0;
              toLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]-dir[k][1], coo[j][1]-dir[k][0]) == 2 && chartGet(chart, temp1-dir[k][1], temp2-dir[k][0]) != 2)
                toLineExtend = 0;
              ctx.moveTo((coo[j][1] + 1/2 + dir[k][1]/2 + dir[k][0]/2 +dir[k][0]*wid*fromLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 + dir[k][1]/2+dir[k][1]*wid*fromLineExtend)*blockSize);
              ctx.lineTo((coo[j][1] + 1/2 + dir[k][1]/2 - dir[k][0]/2 -dir[k][0]*wid*toLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 - dir[k][1]/2-dir[k][1]*wid*toLineExtend)*blockSize);
              ctx.stroke();
            }
          }
        }
        for (j = p10000*10000+(i-1)*1000+1; j <= p10000*10000+i*1000; j++)
          chartSet(chart, coo[j][0], coo[j][1], 3);
      }
      for (i = 1; i <= p100; i++){
        if (p100 == 7) nowColor = i;
        else if (p100 == 9) nowColor = 9 + Math.floor((i-0.5)/3);
        else nowColor = p100;
        for (j = p10000*10000+p1000*1000+(i-1)*100+1; j <= p10000*10000+p1000*1000+i*100; j++){
          chartSet(chart, coo[j][0], coo[j][1], 2);
          if ((coo[j][0]+coo[j][1])%2==0) ctx.fillStyle = hun1[nowColor];
          else ctx.fillStyle = hun2[nowColor];
          ctx.fillRect((coo[j][1]+from)*blockSize, (coo[j][0]+from)*blockSize, blockRatio*blockSize, blockRatio*blockSize);
        }
        ctx.strokeStyle = bord[nowColor];
        for (j = p10000*10000+p1000*1000+(i-1)*100+1; j <= p10000*10000+p1000*1000+i*100; j++){
          for (k=0; k<4; k++){
            temp1 = coo[j][0] + dir[k][0];
            temp2 = coo[j][1] + dir[k][1];
            if (chartGet(chart, temp1, temp2) != 2){
              ctx.beginPath();
              fromLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]+dir[k][1], coo[j][1]+dir[k][0]) == 2 && chartGet(chart, temp1+dir[k][1], temp2+dir[k][0]) != 2)
                fromLineExtend = 0;
              toLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]-dir[k][1], coo[j][1]-dir[k][0]) == 2 && chartGet(chart, temp1-dir[k][1], temp2-dir[k][0]) != 2)
                toLineExtend = 0;
              ctx.moveTo((coo[j][1] + 1/2 + dir[k][1]/2 + dir[k][0]/2 +dir[k][0]*wid*fromLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 + dir[k][1]/2+dir[k][1]*wid*fromLineExtend)*blockSize);
              ctx.lineTo((coo[j][1] + 1/2 + dir[k][1]/2 - dir[k][0]/2 -dir[k][0]*wid*toLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 - dir[k][1]/2-dir[k][1]*wid*toLineExtend)*blockSize);
              ctx.stroke();
            }
          }
        }
        for (j = p10000*10000+p1000*1000+(i-1)*100+1; j <= p10000*10000+p1000*1000+i*100; j++)
          chartSet(chart, coo[j][0], coo[j][1], 3);
      }
      for (i = 1; i <= p10; i++){
        if (p10 == 7) nowColor = i;
        else if (p10 == 9) nowColor = 9 + Math.floor((i-0.5)/3);
        else if (p10 == 1) nowColor = 0;
        else nowColor = p10;
        for (j = p10000*10000+p1000*1000+p100*100+(i-1)*10+1; j <= p10000*10000+p1000*1000+p100*100+i*10; j++){
          chartSet(chart, coo[j][0], coo[j][1], 2);
          ctx.fillStyle = tens[nowColor];
          ctx.fillRect((coo[j][1]+from)*blockSize, (coo[j][0]+from)*blockSize, blockRatio*blockSize, blockRatio*blockSize);
        }
        ctx.strokeStyle = bord[nowColor];
        for (j = p10000*10000+p1000*1000+p100*100+(i-1)*10+1; j <= p10000*10000+p1000*1000+p100*100+i*10; j++){
          for (k=0; k<4; k++){
            temp1 = coo[j][0] + dir[k][0];
            temp2 = coo[j][1] + dir[k][1];
            if (chartGet(chart, temp1, temp2) != 2){
              ctx.beginPath();
              fromLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]+dir[k][1], coo[j][1]+dir[k][0]) == 2 && chartGet(chart, temp1+dir[k][1], temp2+dir[k][0]) != 2)
                fromLineExtend = 0;
              toLineExtend = lineExtend;
              if (chartGet(chart, coo[j][0]-dir[k][1], coo[j][1]-dir[k][0]) == 2 && chartGet(chart, temp1-dir[k][1], temp2-dir[k][0]) != 2)
                toLineExtend = 0;
              ctx.moveTo((coo[j][1] + 1/2 + dir[k][1]/2 + dir[k][0]/2 +dir[k][0]*wid*fromLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 + dir[k][1]/2+dir[k][1]*wid*fromLineExtend)*blockSize);
              ctx.lineTo((coo[j][1] + 1/2 + dir[k][1]/2 - dir[k][0]/2 -dir[k][0]*wid*toLineExtend)*blockSize, (coo[j][0] + 1/2 + dir[k][0]/2 - dir[k][1]/2-dir[k][1]*wid*toLineExtend)*blockSize);
              ctx.stroke();
            }
          }
        }
        for (j = p10000*10000+p1000*1000+p100*100+(i-1)*10+1; j <= p10000*10000+p1000*1000+p100*100+i*10; j++)
          chartSet(chart, coo[j][0], coo[j][1], 3);
        //console.log(chart)
      }
    }
  }

  return {
    drawNumberBlocks: ok,
    translateNumberBlocks: move,
  }
}()
